package xj.graph2d.tree;

import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.swing.*;
import javax.swing.tree.*;

import xj.app.BaseApplicationFrame;
import xj.app.LookAndFeelManager;
import xj.graph2d.BoxGroup;
import xj.graph2d.CompoundDrawShape;
import xj.graph2d.DocumentChangeListener;
import xj.graph2d.DrawCanvas;
import xj.graph2d.DrawShape;
import xj.graph2d.Group;
import xj.graph2d.SimpleDrawDocument;
import xj.graph2d.SimpleDrawView;
import xj.graph2d.TextShape;
import xj.graph2d.app.DrawApp;

public class DocumentTreePane 
  extends JPanel 
  implements DocumentChangeListener {

  public DocumentTreePane(DrawApp main, Map<String, Icon> iconMap) {
    this.main = main;
    if (main != null) {
      doc = main.getDoc();
      view = main.getView();
      if (view != null) {
	canvas = view.getCanvas();
      }
      if (doc != null) {
	doc.setDocumentChangeListener(this);
      }
      setLayout(new BorderLayout());
      scroll = new JScrollPane();
      add(scroll, BorderLayout.CENTER);
      DocumentTreeCellIcons icons = new DocumentTreeCellIcons(iconMap);
      LookAndFeelManager laf = BaseApplicationFrame.getLookAndFeelManager();
      if (laf.isSubstanceLnF()) { 
	treeCellRenderer = new SubstanceTreeCellRenderer(icons);
      } else { 
	treeCellRenderer = new DocumentTreeCellRenderer(icons);
      }
      buildDocumentTree();
    }
  }

  protected void buildDocumentTree() {
    if (doc != null) {
      root = new DocumentTreeNode(doc);
      tree = new JTree(root);
      ToolTipManager.sharedInstance().registerComponent(tree);
      tree.setCellRenderer(treeCellRenderer);
      tree.setShowsRootHandles(true);
      // tree.setShowsRootHandles(false);
      treeListener = new TreeListener();
      tree.addMouseListener(treeListener);
      model = (DefaultTreeModel) tree.getModel();
      scroll.setViewportView(tree);
      validate();

      if (debug) {
	TreeSelectionModel sm = tree.getSelectionModel();
	int mode = sm.getSelectionMode();
	String smode = "unknown";
	switch (mode) {
	case TreeSelectionModel.SINGLE_TREE_SELECTION:
	  smode = "SINGLE_TREE_SELECTION";
	  break;
	case TreeSelectionModel.CONTIGUOUS_TREE_SELECTION:
	  smode = "CONTIGUOUS_TREE_SELECTION";
	  break;
	case TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION:
	  smode = "DISCONTIGUOUS_TREE_SELECTION";
	  break;
	}

	System.out.println("TreeSelectionModel: " + smode);
      }

    }
  }

  protected void refreshTree() {
    removeAllTreeNodes(root);

    root.setDocument(doc);

    Iterator iter = doc.iterator();
    while (iter.hasNext()) {
      DrawShape shape = (DrawShape) iter.next();
      if (shape != null) {
	buildTreeNode(root, shape);
      }
    }

    Group group = doc.getGroup();
    if (group != null) {
      iter = group.allShapes();
      while (iter.hasNext()) {
	DrawShape shape = (DrawShape) iter.next();
	if (shape != null) {
	  buildTreeNode(root, shape);
	}
      }
    }

    TreeNode[] nodes = root.getPath();
    TreePath path = new TreePath(nodes);
    tree.expandPath(path);

    validate();
  }

  protected void removeAllTreeNodes(DefaultMutableTreeNode parent) {
    if (parent != null) {
      int n = parent.getChildCount();
      for (int i = n - 1; i >= 0; i--) {
	TreeNode tn = parent.getChildAt(i);
	if (tn instanceof DrawShapeTreeNode) {
	  DrawShapeTreeNode dtn = (DrawShapeTreeNode) tn;
	  model.removeNodeFromParent(dtn);
	  if (!model.isLeaf(dtn)) {
	    removeAllTreeNodes(dtn);
	  }
	}
      }
    }
  }

  protected DrawShapeTreeNode buildTreeNode(DefaultMutableTreeNode parent,
					    DrawShape shape) {
    return buildTreeNode(parent, shape, -1);
  }

  protected DrawShapeTreeNode buildTreeNode(DefaultMutableTreeNode parent,
					    DrawShape shape, int pos) {
    DrawShapeTreeNode node = null;
    if (parent != null && shape != null) {
      DrawShape container = shape.getParent();
      if (pos == -1) {
	if (container != null) {
	  pos = container.indexOfShape(shape);
	}
      }
      if (pos < 0 || pos > parent.getChildCount()) {
	pos = parent.getChildCount();
      }
      node = new DrawShapeTreeNode(shape);
      model.insertNodeInto(node, parent, pos);

      // if (!shape.isAtomic()) {
      // !(shape instanceof BoxLabel) &&

      if ((showAtomicDeatil || 
	   !shape.isAtomic()) && shape.getShapeCount() > 0) {
	Iterator citer = shape.allShapes();
	while (citer.hasNext()) {
	  DrawShape s = (DrawShape) citer.next();
	  if (s != null && s.isVisible()) {
	    if (s instanceof TextShape) {
	      TextShape text = (TextShape) s;
	      String str = text.getString();
	      if (str != null && str.length() > 0) {
		buildTreeNode(node, s);
	      }
	    } else {
	      buildTreeNode(node, s);
	    }
	  }
	}
      }
    }

    return node;
  }

  protected DrawShapeTreeNode findTreeNode(DrawShape shape) {
    return findTreeNode(root, shape);
  }

  protected DrawShapeTreeNode findTreeNode(DefaultMutableTreeNode parent,
      DrawShape shape) {
    if (parent != null && shape != null) {
      int n = parent.getChildCount();
      for (int i = 0; i < n; i++) {
	TreeNode tn = parent.getChildAt(i);
	if (tn instanceof DrawShapeTreeNode) {
	  DrawShapeTreeNode dtn = (DrawShapeTreeNode) tn;
	  if (shape == dtn.getShape()) {
	    return dtn;
	  }
	  if (!dtn.isLeaf()) {
	    DrawShapeTreeNode cn = findTreeNode(dtn, shape);
	    if (cn != null) {
	      return cn;
	    }
	  }
	}
      }
    }
    return null;
  }

  public void shapeChanged(DrawShape shape) {
    if (shape != null) {
      DrawShapeTreeNode n = findTreeNode(shape);
      if (n != null) {
	// model.nodeChanged(n);
	DefaultMutableTreeNode pnode = (DefaultMutableTreeNode) n.getParent();
	int i = pnode.getIndex(n);
	model.removeNodeFromParent(n);
	n = buildTreeNode(pnode, shape, i);

	TreeNode[] nodes = n.getPath();
	TreePath path = new TreePath(nodes);
	tree.setSelectionPath(path);
      } else {
	DrawShape p = shape.getParent();
	while (p != null) {
	  n = findTreeNode(p);
	  if (n != null) {
	    DefaultMutableTreeNode pnode = (DefaultMutableTreeNode) n.getParent();
	    int i = pnode.getIndex(n);
	    model.removeNodeFromParent(n);
	    // n = buildTreeNode(pnode, shape, i);
	    n = buildTreeNode(pnode, p, i);

	    DrawShapeTreeNode n1 = findTreeNode(shape);
	    if (n1 != null) { 
	      n = n1;
	    }
	    TreeNode[] nodes = n.getPath();
	    TreePath path = new TreePath(nodes);
	    tree.setSelectionPath(path);
	    break;
	  } else {
	    // shape = p;
	    // p = shape.getParent();
	    p = p.getParent();
	  }
	}
      }
    }
  }

  public void shapeSelected(DrawShape shape) {
    if (debug) { 
      System.out.println("DocumentTreePanel.shapeSelected() @1");
      if (shape != null) { 
	System.out.println("DocumentTreePanel.shapeSelected() templateName=" + shape.getTemplateName());
      }
    }

    if (shape instanceof Group) {
      Group g = (Group) shape;
      int n = g.getShapeCount();
      TreePath[] paths = new TreePath[n];
      for (int i = 0; i < n; i++) {
	DrawShape s = g.getShape(i);
	if (s != null) {
	  DrawShapeTreeNode tn = findTreeNode(s);
	  if (tn != null) {
	    TreeNode[] nodes = tn.getPath();
	    paths[i] = new TreePath(nodes);
	  }
	}
      }
      tree.setSelectionPaths(paths);
      if (main != null) {
	main.showShapeProperties(null);
      }
    } else {
      DrawShapeTreeNode n = findTreeNode(shape);
      if (n != null) {
	TreeNode[] nodes = n.getPath();
	TreePath path = new TreePath(nodes);
	tree.setSelectionPath(path);
      }

      if (main != null) {
	main.showShapeProperties(shape);
      }
    }
    // System.out.println("DocumentTreePanel.shapeSelected() @2");
  }

  public void shapeAdded(DrawShape shape) {
    // System.out.println("DocumentTreePane.shapeAdded()");

    if (shape != null) {
      DrawShape parent = shape.getParent();
      DrawShapeTreeNode n;
      if (parent != null) {
	DrawShapeTreeNode pnode = findTreeNode(parent);
	n = buildTreeNode(pnode, shape);
      } else {
	n = buildTreeNode(root, shape);
      }
      if (n != null) {
	TreeNode[] nodes = n.getPath();
	TreePath path = new TreePath(nodes);
	tree.setSelectionPath(path);
	validate();
      }

      if (main != null) {
	main.showShapeProperties(shape);
      }
    }
  }

  public void shapeRemoved(DrawShape shape) {
    DrawShapeTreeNode n = findTreeNode(shape);
    if (n != null) {
      model.removeNodeFromParent(n);
      validate();

      if (shape == doc.getSelectedShape()) {
	if (main != null) {
	  main.showShapeProperties(null);
	}
      }
    }
  }

  public void selectionSetUpdated() {
    Set<DrawShape> sel = doc.getSelectionSet();
    int count = sel.size();
    TreePath[] paths = new TreePath[count];
    int j = 0;
    Iterator<DrawShape> iter = sel.iterator();
    while (iter.hasNext()) {
      DrawShape s = iter.next();
      if (s != null) {
	DrawShapeTreeNode tn = findTreeNode(s);
	if (tn != null) {
	  TreeNode[] nodes = tn.getPath();
	  paths[j++] = new TreePath(nodes);
	}
      }
    }
    tree.setSelectionPaths(paths);
    if (main != null) {
      main.showShapeProperties(null);
    }
  }

  public void clear() {
    removeAllTreeNodes(root);
    validate();
  }

  public void refresh() {
    refreshTree();
  }

  public void reset() {
    if (main != null) {
      doc = main.getDoc();
      view = main.getView();
      if (view != null) {
	canvas = view.getCanvas();
      }
      if (doc != null) {
	doc.setDocumentChangeListener(this);
      }
      refreshTree();

      main.showShapeProperties(null);
    }
  }

  protected DrawApp main;

  protected SimpleDrawDocument doc;

  protected SimpleDrawView view;

  protected DrawCanvas canvas;

  protected JTree tree;

  protected TreeCellRenderer treeCellRenderer; 

  protected DocumentTreeNode root;

  protected DefaultTreeModel model;

  protected JScrollPane scroll;

  protected MouseListener treeListener;

  protected TreeNode curNode = null;

  protected boolean showAtomicDeatil = true;

  protected static final boolean debug = false;

  class TreeListener extends MouseAdapter {
    protected int selRow;

    protected TreePath selPath;

    // protected DrawShapePropertyDialog dialog;

    TreeListener() {
    }

    @Override
    public void mousePressed(MouseEvent e) {
      int button = e.getButton();

      DrawShape prevSelectedShape = doc.getSelectedShape();
      if (prevSelectedShape != null) {
	doc.setSelectedShape(null, false);
	canvas.repaintShape(prevSelectedShape, true);
      }
      Set<DrawShape> sel = doc.getSelectionSet();
      if (!sel.isEmpty()) {
	// unselect all shapes
	doc.clearSelectionSet();
	canvas.repaint();
      }

      int x = e.getX();
      int y = e.getY();
      selRow = tree.getRowForLocation(x, y);
      int modifiers = e.getModifiers();
      if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
	TreePath[] paths = tree.getSelectionPaths();
	if (paths != null && paths.length > 0) {
	  for (int i = 0; i < paths.length; i++) {
	    DrawShapeTreeNode snode = (DrawShapeTreeNode) paths[i]
		.getLastPathComponent();
	    DrawShape shape = snode.getShape();
	    if (shape != null) {
	      sel.add(shape);
	      canvas.repaintShape(shape, true);
	    }
	  }
	}
      } else {
	selPath = tree.getPathForLocation(x, y);
	if (selRow != -1) {
	  DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
	  int pathLen = selPath.getPathCount();
	  if (pathLen > 0) {
	    curNode = (TreeNode) selPath.getLastPathComponent();
	    if (curNode instanceof DrawShapeTreeNode) {
	      DrawShapeTreeNode snode = (DrawShapeTreeNode) curNode;
	      DrawShape shape = snode.getShape();
	      DrawShape parent = shape.getParent();

	      // System.out.println("DocumentTreePane.TreeListener
	      // shape=" + shape);

	      if (parent instanceof BoxGroup) {
		// multi-line text
		doc.setSelectedShape(shape, false);
		canvas.repaintShape(shape);

		if (main != null) {
		  main.showShapeProperties(shape);
		}
	      } else if (parent instanceof Group) {
		doc.selectGroup((Group) parent);
		canvas.repaintShape(parent);

		if (main != null) {
		  main.showShapeProperties(null);
		}
	      } else {
		doc.setSelectedShape(shape, false);
		canvas.repaintShape(shape);

		if (main != null) {
		  if (!(shape instanceof CompoundDrawShape)) {
		    main.showShapeProperties(shape);
		  } else {
		    main.showShapeProperties(null);
		  }
		}
	      }

	      if (e.isPopupTrigger() || button == MouseEvent.BUTTON3) {
		// right mouse button
		JPopupMenu popup = makePopupMenu();
		if (popup != null) {
		  popup.show(tree, x, y);
		}
	      }
	    } else {
	      if (main != null) {
		main.showShapeProperties(null);
	      }
	    }
	  }
	}
      }
    }

  }

  JPopupMenu makePopupMenu() {
    JPopupMenu popup = new JPopupMenu();
    JMenuItem mi;
    popup.add(mi = new JMenuItem("Delete"));
    // Actoion: main.cut();
    ActionListener editAction = DrawApp.getAction("Edit");
    mi.setActionCommand("Cut");
    mi.addActionListener(editAction);
    return popup;
  }

}
